package lion.workflow;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import lion.dev.lang.KV;
import lion.dev.lang.MapJ;
import lion.dev.web.Validator;
import lion.framework.core.conf.Config;
import lion.framework.core.conf.ConfigManager;
import lion.framework.core.db.DB;
import lion.workflow.enums.FlowStatus;
import lion.workflow.enums.FlowTaskStatus;
import lion.workflow.enums.ProcessStatus;
import lion.workflow.model.FlowProcess;
import lion.workflow.model.FlowState;
import lion.workflow.model.FlowTask;
import lion.workflow.model.WorkFlowModel;

/**
 *
 * 顺序任务处理
 *
 * 回退任务处理
 *
 * @author lion
 * @email hl_0074@sina.com
 * @date 2014年7月24日
 */
public class WorkflowContext implements Closeable {

	//
	private static Map<Integer, WorkFlow> workFlows = new HashMap<>();

	private WorkFlow workFlow;
	private WorkFlowState workFlowState;
	private WorkFlowTransition transition;
	private String toState;
	private FlowProcess process;
	private Object data;
	private MapJ variables = new MapJ();
	private DB db;

	private FlowState flowState;

	public WorkflowContext(int processId) {

		this.db = getWorkFlowDataSource();
		this.process = FlowProcess.load(processId, this.db);
		this.workFlow = getWorkFlow(this.process.getWorkFlowId());
	}

	/**
	 * 某用户在某个状态执行了某个操作，使用了某些变量。
	 */
	public void handle(int userId, int taskId, String transitionName, MapJ variables) {

		FlowTask flowTask = FlowTask.load(taskId, this.db);
		if (flowTask == null || flowTask.getUserId() != userId) { throw new WorkFlowException("task not found"); }

		prepareAction(userId, flowTask.getStateId(), variables);

		WorkFlowTransition workFlowTransition = this.workFlowState.getTransition(transitionName);
		if (workFlowTransition == null) { throw new WorkFlowException("transition not found in current state :" + this.workFlowState + "->" + transitionName); }
		this.transition = workFlowTransition;

		// 更新任务信息
		this.db.update(FlowTask.TABLE_NAME, new KV<>("id", taskId), new KV<>("transition", transitionName), new KV<>("finishTime", new Date()),
				new KV<>("note", variables.getString("note")), new KV<>("status", FlowTaskStatus.FINISHED));

		// 检查是否所有人已经执行(会签)
		if (!checkWait(transitionName, this.workFlowState)) { return; }

		// 结束当前状态
		finishState(this.flowState, FlowStatus.FINISH);

		// 进入下一个状态
		moveToNextState(userId);
	}

	/**
	 * 回退操作
	 */
	public void rollback(int userId, int stateId, String toState, MapJ variable) {

		WorkFlowState wfs = this.workFlow.getState(toState);
		if (wfs == null) { throw new WorkFlowException("workflow state not found : " + toState); }

		prepareAction(userId, stateId, variable);

		// 结束当前的状态
		finishState(this.flowState, FlowStatus.ROLLBACK);

		// 如果是回退，则结束同源分支下的其他状态
		String tag = null, parentTag = null;
		if (StringUtils.isNotBlank(this.flowState.getTag())) {

			MapJ param = new MapJ();
			param.put("tag", this.flowState.getTag());
			param.put("parentTag", this.flowState.getParentTag());
			param.put("state", toState);

			// 可以回退到本分支，或回退到当前分支的主干
			FlowState to = FlowState.load("where state=:state and endTime!=null and (tag=:tag or tag=:parentTag) order by endTime desc", param, this.db);
			if (to == null) { throw new WorkFlowException("unable to rollback to an state has never been executed"); }
			tag = to.getTag();
			parentTag = to.getParentTag();

			// 如果退回到主干上面
			if (!StringUtils.equals(to.getTag(), this.flowState.getTag())) { //
				// 删除同源分支下的未处理的任务
				this.db.update("delete from flowtask ft where status=? and exists(select 1 from flowstates fs where ft.stateId=fs.id and fs.status=? and fs.tag=:tag)",
						FlowTaskStatus.UNDERWAY, FlowStatus.UNDERWAY, this.flowState.getTag());
				// 结束同源分支下未完成的状态
				this.db.update("update flowstate set status=?,token=null,endTime=? where tag=? and status =?", FlowStatus.ROLLBACK, new Date(), this.flowState.getTag(),
						FlowStatus.UNDERWAY);
			}
		}

		// 保存新的状态
		this.workFlowState = wfs;
		this.flowState = createFlowState(userId, tag, parentTag, wfs);
		// execute actions TODO

		this.workFlow.getHandler().onStateChanged(this);
	}

	/**
	 * 重新开始流程
	 */
	public void restart(int userId) {

		// 如果已经结束则不充许重启
		if (!this.process.getStatus().equals(ProcessStatus.UNDERWAY)) { throw new WorkFlowException("work flow is end"); }

		// 删除未完成的任务
		this.db.update("delete from flowtask where processId=? and status=?", this.process.getId(), FlowTaskStatus.UNDERWAY);

		// 结束状态
		this.db.update("update flowstate set status=? ,endTime=?,token=null,where processId=? and status=?", FlowStatus.CANCELED, new Date(), this.process.getId(),
				FlowStatus.UNDERWAY);

		// 回退到开始状态
		this.workFlowState = this.workFlow.getStartState();
		this.flowState = createFlowState(this.process.getCreator(), null, null, this.workFlow.getStartState());

		this.workFlow.getHandler().onStateChanged(this);
	}

	/**
	 * 终止流程
	 */
	public void terminate(int userId) {

		// 删除未完成的任务
		this.db.update("delete from flowtask where processId=? and status=?", this.process.getId(), FlowTaskStatus.UNDERWAY);

		// 结束状态
		this.db.update("update flowstate set status=? ,endTime=?,token=?,where processId=? and status<>?", FlowStatus.CANCELED, new Date(), null, this.process.getId(),
				FlowStatus.UNDERWAY);

		// 结束流程
		this.process.setStatus(ProcessStatus.TERMINATED);
		this.process.update(this.db);
		this.workFlowState = this.workFlow.getEndState();

		this.workFlow.getHandler().onTerminated(this);
	}

	/**
	 * 启动
	 */
	public void start(int userId) {

		this.db.update(FlowProcess.TABLE_NAME, new KV<>("id", this.process.getId()), new KV<>("status", ProcessStatus.UNDERWAY));
		WorkFlowState wfs = this.workFlow.getStartState();
		this.flowState = createFlowState(userId, null, null, wfs);

		this.workFlow.getHandler().onStart(this);
	}

	public static void createAndStartProcess(int userId, int dataId, int workflowId) {

		FlowProcess fp = createProcess(userId, dataId, workflowId);

		WorkflowContext wfc = new WorkflowContext(fp.getId());
		try {
			wfc.start(userId);
		} finally {
			wfc.close();
		}
	}

	public static FlowProcess createProcess(int userId, int dataId, int workFlowId) {

		FlowProcess fp = new FlowProcess();
		fp.setStatus(ProcessStatus.PENDING);
		fp.setCreateTime(new Date());
		fp.setCreator(userId);
		fp.setDataId(dataId);
		fp.setToken(UUID.randomUUID().toString().replace("-", ""));
		fp.setWorkFlowId(workFlowId);
		DB db = getWorkFlowDataSource();
		try {
			fp.save(db);
		} finally {
			db.close();
		}

		return fp;
	}

	public List<String> getTransitions(int userId, String state) {

		WorkFlowState workflowState = this.workFlow.getState(this.flowState.getState());
		if (workflowState == null || workflowState.isEnd()) { return Collections.EMPTY_LIST; }

		if (!workflowState.hasPriv(userId, getWorkFlow().getHandler().getUserRole(userId))) { return Collections.EMPTY_LIST; }

		List<WorkFlowTransition> wfts = workflowState.getTransitions();
		List<String> result = new ArrayList<>();
		for (WorkFlowTransition wft : wfts) {
			result.add(wft.getName());
		}

		return result;
	}

	/**
	 * 获得所有工作流
	 */
	public static List<WorkFlow> getWorkFlows() {

		if (workFlows.isEmpty()) {

			List<WorkFlowModel> flows = null;
			DB db = getWorkFlowDataSource();
			try {
				flows = WorkFlowModel.list(-1, db);
			} finally {
				db.close();
			}
			for (WorkFlowModel wf : flows) {
				workFlows.put(wf.getId(), new WorkFlow(wf));
			}
		}
		return Collections.unmodifiableList(new ArrayList<>(workFlows.values()));
	}

	/**
	 * 将流程状态移动到下一个状态，
	 *
	 * 状态不能即是分支又是汇合点
	 */
	private void moveToNextState(int userId) {

		String nextState = this.transition.findToState(this);

		// 如果结点没有可以执行的下一个节点。结束流程
		if (StringUtils.isBlank(nextState)) {
			completeProcess();
			return;
		}

		String[] tos = StringUtils.split(nextState, ",");

		String tag = null;
		String parentTag = null;
		if (tos.length == 1) {
			WorkFlowState nextWorkFlowState = this.workFlow.getState(tos[0]);
			if (StringUtils.isNotBlank(this.flowState.getTag()) && StringUtils.isNotBlank(nextWorkFlowState.getJoin()) && !checkJoin(nextWorkFlowState)) { return; } // 检查汇合点。
			if (StringUtils.isNotBlank(nextWorkFlowState.getJoin())) { // 聚合点
				tag = this.flowState.getParentTag();
				parentTag = this.db.queryOne("select parentTag from flowstate where tag=?", this.flowState.getParentTag());
			}
		} else {
			// 分支点
			parentTag = this.flowState.getTag();
			tag = UUID.randomUUID().toString().replace("-", "");
		}
		this.toState = nextState;
		for (String n : tos) {
			if (StringUtils.isBlank(n)) {
				continue;
			}
			WorkFlowState wfs = this.workFlow.getState(n);
			if (wfs == null) {
				continue;
			}

			if (wfs.isEnd()) {
				completeProcess();
				return;
			}

			createFlowState(userId, tag, parentTag, wfs);
			// execute preactions
			this.workFlow.getHandler().onStateChanged(this);
		}
	}

	/**
	 * 创建流程状态节点
	 */
	private FlowState createFlowState(int userId, String tag, String parentTag, WorkFlowState wfs) {

		FlowState fs = new FlowState();
		fs.setCreateTime(new Date());
		fs.setCreator(userId);
		fs.setProcessId(this.process.getId());
		fs.setState(this.toState);
		fs.setStatus(FlowStatus.UNDERWAY);
		fs.setToken(this.process.getToken());
		fs.setTag(tag);
		fs.setParentTag(parentTag);
		fs.save(this.db);

		// create taskIds
		createFlowStateTask(wfs, fs);

		return fs;
	}

	public static WorkFlow getWorkFlow(int workFlowId) {

		if (workFlows.isEmpty()) {
			getWorkFlows();
		}

		WorkFlow wFlow = workFlows.get(workFlowId);

		return wFlow;
	}

	/**
	 * 创建流程任务
	 *
	 * 如果是开始节点，则只为流程发起者创建任务
	 */
	private void createFlowStateTask(WorkFlowState wfs, FlowState fs) {

		int[] accessUsers = null;
		if (!wfs.isStart()) {
			accessUsers = this.workFlow.getHandler().getUsers(Validator.toIntArray(wfs.getUsers()), Validator.toIntArray(wfs.getRoles()));
		} else {
			accessUsers = new int[] { this.process.getCreator() };
		}

		for (int uid : accessUsers) {
			FlowTask task = new FlowTask();
			task.setCreateTime(new Date());
			task.setProcessId(fs.getProcessId());
			task.setStatus(FlowTaskStatus.UNDERWAY);
			task.setUserId(uid);
			task.setStateId(fs.getId());
			task.save(this.db);
		}
	}

	private void prepareAction(int userId, int stateId, MapJ variables) {

		this.variables = variables;
		if (ProcessStatus.COMPLETE.equals(this.process.getStatus())
				|| ProcessStatus.TERMINATED.equals(this.process.getStatus())) { throw new WorkFlowException("process has completed or terminated "); }

		FlowState flowState = FlowState.load(stateId, this.db);
		if (flowState == null) { throw new WorkFlowException("step not found :" + stateId); }
		this.flowState = flowState;

		WorkFlowState workflowState = this.workFlow.getState(this.flowState.getState());
		if (workflowState == null) { throw new WorkFlowException("state not in current workflow : " + this.workFlowState); }
		if (workflowState.isEnd()) { throw new WorkFlowException("current work flow state is the end of the work flow: " + this.workFlowState); }
		this.workFlowState = workflowState;

		// check the privleges
		if (!workflowState.hasPriv(userId,
				this.workFlow.getHandler().getUserRole(userId))) { throw new WorkFlowException("No permissions on current state:" + this.workFlowState.getName()); }
	}

	private void finishState(FlowState state, FlowStatus flowStatus) {

		state.setStatus(flowStatus);
		state.setEndTime(new Date());
		state.setToken(null);
		state.update(this.db);

		// 当状态结束时，删除状态中未处理的任务
		this.db.update("delete from flowtask  where stateId=? and status=?", state.getId(), FlowTaskStatus.UNDERWAY);

		// execute post actions
	}

	// 结束流程
	private void completeProcess() {

		// 结束未完成的任务
		this.db.update("delete from flowtask where processId=? and status=?", this.process.getId(), FlowTaskStatus.UNDERWAY);

		// 结束流程状态
		this.db.update("update flowstate set status=? where processId=? and status=?", FlowStatus.FINISH, this.process.getId(), FlowStatus.UNDERWAY);
		this.db.update("update flowstate set token=null where processId=?", this.process.getId());

		// 结束流程
		this.db.update("update flowprocess set status=:status where id=?", ProcessStatus.COMPLETE, this.process.getId());

		getWorkFlow().getHandler().onComplete(this);
	}

	/**
	 * 检查流程状态是否满足分支聚合
	 *
	 * 只有同源分支的状态可以汇合
	 */
	private boolean checkJoin(WorkFlowState nextWorkFlowState) {

		// 检查是否其他聚合分支条件也已经完成
		String joins = nextWorkFlowState.getJoin();
		if (StringUtils.isBlank(joins)) { return true; }

		// 没有分支，当然没有汇合
		String tag = this.flowState.getTag();
		if (StringUtils.isBlank(tag)) { return true; }

		// 检查当前流程中正在执行的状态是否全部已经完成
		MapJ param = new MapJ();
		param.put("processId", this.process.getId());
		param.put("tag", this.flowState.getTag());
		param.put("status", FlowStatus.FINISH);
		List<FlowState> flowStates = FlowState.list(0, -1, "where processId=:processId and tag=:tag and status=:status", param, this.db);

		// 有未执行完成的
		String[] joinStates = StringUtils.split(joins, ",");
		if (flowStates.size() != joinStates.length) { return false; }

		// 判断每个分支状态的名称和状态是否一致并且已经全部完成
		for (FlowState fs : flowStates) {
			if (!ArrayUtils.contains(joinStates, fs.getState())) { return false; } // 不在要聚合的状态中,表明该状态还没有到达要聚合的时候。
		}
		return true;
	}

	/**
	 * 检查会签状态
	 */
	private boolean checkWait(String transitionName, WorkFlowState workflowState) {

		if (!workflowState.isWait()) { return true; }

		MapJ param = new MapJ();
		param.put("stateId", this.flowState.getId());
		param.put("transition", transitionName);

		int total = FlowTask.count("where stateId=:stateId");
		int count = FlowTask.count("where stateId=:stateId and transition=:transition", param, this.db);

		double percent = 1.0 * count / total;
		if (percent < this.transition.getThreshold()) { return false; }

		return true;
	}

	private static DB getWorkFlowDataSource() {

		Config config = ConfigManager.getConfig("framework");
		String dataSource = config.getString("framework.workflow.datasource");

		return new DB(dataSource);
	}

	public WorkFlowState getState() {

		return this.workFlowState;
	}

	public WorkFlowTransition getTransition() {

		return this.transition;
	}

	public String getToState() {

		return this.toState;
	}

	public FlowProcess getProcess() {

		return this.process;
	}

	public Object getData() {

		if (this.data == null) {
			this.data = this.workFlow.getHandler().getData(this.process.getDataId());
		}
		return this.data;
	}

	public MapJ getVariables() {

		return this.variables;
	}

	public void setVariable(String name, Object value) {

		this.variables.put(name, value);
	}

	public WorkFlow getWorkFlow() {

		return this.workFlow;
	}

	@Override
	public void close() {

		this.db.close();
	}
}
